﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace MvvmBase.ViewModel
{
    public class ObservableCollectionWrapper<TInput, TOutput> : IEnumerable<TOutput>, INotifyCollectionChanged,
                                                                IDisposable, IDictionary<TInput, TOutput>
    {
        private readonly Dictionary<TInput, TOutput> _internalCollection;
        private readonly ObservableCollection<TInput> _inputCollection;
        private readonly Func<TInput, TOutput> _createOutputValue;

        public ObservableCollectionWrapper(ObservableCollection<TInput> inputCollection,
                                           Func<TInput, TOutput> createOutputValue)
        {
            _inputCollection = inputCollection;
            _createOutputValue = createOutputValue;
            _inputCollection.CollectionChanged += inputCollectionChanged;

            _internalCollection = new Dictionary<TInput, TOutput>(inputCollection.Count);
            FillInternalCollection();
        }

        
        /*todo: Нужен ли он
         * private void Refresh()
        {
            Reset();
            FillInternalCollection();
        }*/

        private void Reset()
        {
            foreach (var value in _internalCollection.Values)
            {
                var disposable = value as IDisposable;
                if (disposable != null)
                    (disposable).Dispose();
            }

            _internalCollection.Clear();
        }

        private void FillInternalCollection()
        {
            foreach (var input in _inputCollection)
            {
                var output = _createOutputValue(input);
                _internalCollection.Add(input, output);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,output,_internalCollection.Count-1));
            }
        }

        private void inputCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Reset:
                    _internalCollection.Clear();
                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    break;
                case NotifyCollectionChangedAction.Add:
                    var newItem = (TInput) e.NewItems[0];
                    _internalCollection.Add(newItem, _createOutputValue(newItem));
                    break;
                default:
                    throw new InvalidOperationException("Not supported collection modification");
            }
        }

        IEnumerator<KeyValuePair<TInput, TOutput>> IEnumerable<KeyValuePair<TInput, TOutput>>.GetEnumerator()
        {
            return _internalCollection.GetEnumerator();
        }

        public IEnumerator<TOutput> GetEnumerator()
        {
            return _internalCollection.Values.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        public void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler handler = CollectionChanged;
            if (handler != null) handler(this, e);
        }

        public void Dispose()
        {
            _inputCollection.CollectionChanged -= inputCollectionChanged;
        }

        public void Add(KeyValuePair<TInput, TOutput> item)
        {
            _internalCollection.Add(item.Key, item.Value);
        }

        public void Clear()
        {
            _internalCollection.Clear();
        }

        public bool Contains(KeyValuePair<TInput, TOutput> item)
        {
            return _internalCollection.ContainsKey(item.Key) && _internalCollection.ContainsValue(item.Value);
        }

        public void CopyTo(KeyValuePair<TInput, TOutput>[] array, int arrayIndex)
        {
            throw new NotSupportedException();
        }

        public bool Remove(KeyValuePair<TInput, TOutput> item)
        {
            throw new NotSupportedException();
        }

        public int Count
        {
            get { return _internalCollection.Count; }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public bool ContainsKey(TInput key)
        {
            return _internalCollection.ContainsKey(key);
        }

        public void Add(TInput key, TOutput value)
        {
            throw new NotSupportedException();
        }

        public bool Remove(TInput key)
        {
            throw new NotSupportedException();
        }

        public bool TryGetValue(TInput key, out TOutput value)
        {
            return _internalCollection.TryGetValue(key, out value);
        }

        public TOutput this[TInput key]
        {
            get { return _internalCollection[key]; }
            set { throw new NotSupportedException(); }
        }

        public ICollection<TInput> Keys
        {
            get { return _internalCollection.Keys; }
        }

        public ICollection<TOutput> Values
        {
            get { return _internalCollection.Values; }
        }
    }
}